﻿namespace C11_Ex02_1
{
    using System;
    using System.Collections.Generic;
    using System.Text;

    /// <summary>
    /// This class contains all of the functions needed to set the GUI for the card memory game for windows console interface.
    /// </summary>
    /// <remarks>
    /// 1. For entering the chars "q" or "Q" the game ends immediately.
    /// </remarks>
    public class UserIOInterfaceForConsole
    {
        private CardGameLogics CardGame = null;
        
        /// <summary>
        /// This is the method which handles the game for the console enviroment.
        /// </summary>
        /// <remarks>
        /// 1. It creates variables with which to store data on the game board (who's values are red from the user).
        /// </remarks>
        public void RunGame()
        {
            bool IsComputerMove = false;
            int boardRows = 0;
            int boardColumns = 0;
            BoardPosition SelectedPosition = null;

            CardGame = new CardGameLogics();
            Console.WriteLine("Welcome to the memory game, please enter your name (and press enter) : ");
            CardGame.Player1Name = userRequstToQuit(Console.ReadLine());
            CardGame.Player2type = getOpponentType();
            if (CardGame.Player2type == Opponent.eTypeOfOpponent.Human)
            {
                Console.WriteLine("Enter Player2 Name:");
                CardGame.Player2Name = userRequstToQuit(Console.ReadLine());
            }
            else
            {
                CardGame.Player2Name = "Computer";
                if (!CardGame.CreateComputer())
                {
                    Console.WriteLine("Computer Could not be created");
                }
            }

            getBoardDimentionsFromUser(out boardRows, out boardColumns);
            CardGame.CreateBoard(boardRows, boardColumns);
            Ex02.ConsoleUtils.Screen.Clear();
            do
            {
                do
                {
                    drawCardMatrix(CardGame.Board);
                    if ((CardGame.Player2type == Opponent.eTypeOfOpponent.Human) || (CardGame.Player1Turn()))
                    {
                        Console.WriteLine("{0}'s first move", CardGame.Player1Turn() ?  CardGame.Player1Name : CardGame.Player2Name);
                        getPlayerSelectedCell(out SelectedPosition);
                        IsComputerMove = false;
                    }
                    else
                    {
                        SelectedPosition = CardGame.ComputerMove();
                        IsComputerMove = true;
                    }

                    if (SelectedPosition == null)
                    {
                        break;
                    }

                    CardGame.DoAndSaveMoves(SelectedPosition);
                    Ex02.ConsoleUtils.Screen.Clear();
                    drawCardMatrix(CardGame.Board);
                    if (IsComputerMove)
                    {
                        Console.WriteLine("Computer's First Move,");
                        Console.WriteLine("press enter to resume");
                        Console.ReadLine();
                    }

                    if ((CardGame.Player2type == Opponent.eTypeOfOpponent.Human) || (CardGame.Player1Turn()))
                    {
                        Console.WriteLine("{0}'s second move,", CardGame.Player1Turn() ? CardGame.Player1Name : CardGame.Player2Name);
                        getPlayerSelectedCell(out SelectedPosition);
                    }
                    else
                    {
                        SelectedPosition = CardGame.ComputerMove();
                    }

                    if (SelectedPosition == null)
                    {
                        break;
                    }
                
                    CardGame.DoAndSaveMoves(SelectedPosition);
                    Ex02.ConsoleUtils.Screen.Clear();
                    drawCardMatrix(CardGame.Board);
                    CardGame.TestRoundResult();
                    if (IsComputerMove)
                    {
                        Console.WriteLine("Computer's second Move,");
                    }

                    Console.WriteLine("press enter to resume");
                    Console.ReadLine();
                    Ex02.ConsoleUtils.Screen.Clear();
                } while (!CardGame.WasMacthEnded());

                Ex02.ConsoleUtils.Screen.Clear();
                Console.WriteLine("The winner is {0} with {1} pairs,", 
                    CardGame.WinnerInMatch, CardGame.WinnerPairs);
                CardGame.IncreaseWinnerGamesWonAmount();
                Console.WriteLine("Total scores :{0}Player 1 : {1}{0}Player 2 : {2}",
                    Environment.NewLine, CardGame.Player1GamesWonAmount, CardGame.Player2GamesWonAmount);

            } while (doesUserWantsAnotherMatch(out boardRows, out boardColumns));
        }

        /// <summary>
        /// This method returns a player name.
        /// </summary>
        /// <remarks>
        /// 1. No input checking done here, the player's name is stored as red from the user.
        /// </remarks>
        /// <returns>
        /// This method returns a string which contains the current player name.
        /// </returns>
        private string getPlayerName()
        {
            string ReturnValue = null;

            ReturnValue = userRequstToQuit(Console.ReadLine());

            return ReturnValue;
        }

        /// <summary>
        /// This method reads from the user his desired opponent type.
        /// </summary>
        /// <remarks>
        /// 1. There are currently 4 options for an opponent : Human and 3 levels of computer,
        ///     each one with a different level of AI.
        /// </remarks>
        /// <returns>
        /// This function's return variable is the opponent type which is an Enum.
        /// </returns>
        private Opponent.eTypeOfOpponent getOpponentType()
        {
            Opponent.eTypeOfOpponent ReturnValue = Opponent.eTypeOfOpponent.Human;
            string UserInputForPlayerType = null;

            Console.WriteLine("Please choose a number from the following options :");
            Console.WriteLine("1 To play agains a human player.");
            Console.WriteLine("2 To play agains a easy computer player.");
            Console.WriteLine("3 To play agains a normal computer player.");
            Console.WriteLine("4 To play agains a hard computer player.");
            UserInputForPlayerType = userRequstToQuit(Console.ReadLine());
  
            if (UserInputForPlayerType == "1")
            {
                ReturnValue = Opponent.eTypeOfOpponent.Human;
            }
            else if (UserInputForPlayerType == "2")
            {
                ReturnValue = CardGame.Player2type =Opponent.eTypeOfOpponent.ComputerEasy ;
            }
            else if (UserInputForPlayerType == "3")
            {
                ReturnValue = CardGame.Player2type = Opponent.eTypeOfOpponent.ComputerNormal;
            }
            else if (UserInputForPlayerType == "4")
            {
                ReturnValue = CardGame.Player2type = Opponent.eTypeOfOpponent.ComputerHard;
            }
            else
            {
                Console.WriteLine("Error : input is illegal, please note to enter your choice in the correct format.");
                ReturnValue = getOpponentType();
            }

            return ReturnValue;
        }

        /// <summary>
        /// This function reads the 2 board dimentions from the user (Rows and Columns).
        /// </summary>
        /// <remarks>
        /// 1. This function checks for a legal (even) cards amount so the card could be delt on the board.
        /// </remarks>
        /// <seealso cref="getBoardDimention(string)">
        /// The function above is being called from this function and handles the aquiring of the board's dimentions.
        /// </seealso>
        /// <param name="o_BoardRows">
        /// This parameter is the board's rows amount
        /// </param>
        /// <param name="o_BoardColumns">
        /// This parameter is the board's columns amount
        /// </param>
        private void getBoardDimentionsFromUser(out int o_BoardRows, out int o_BoardColumns)
        {
            o_BoardRows = getBoardDimention("Rows");
            o_BoardColumns = getBoardDimention("Columns");

            if (!CardGame.DoesBoardContainsEvenSquaresAmount(o_BoardColumns, o_BoardRows))
            {
                Console.WriteLine("Error : cards amount isn't even !");
                getBoardDimentionsFromUser(out o_BoardRows, out o_BoardColumns);
            }
        }

        /// <summary>
        /// This funtion handles the reading of a certain board's dimentions.
        /// </summary>
        /// <remarks>
        /// This function checks the correctness of the input and that it doesn't exceeds a certain bound.
        /// </remarks>
        /// <param name="i_DimentionName">
        /// This parameter is the input string red from the user.
        /// </param>
        /// <returns>
        /// The function returns an int which is the convertion of the mentioned above parameter.
        /// </returns>
        private int getBoardDimention(String i_DimentionName)
        {
            bool InputVerified = false;
            int ReturnValue = 0;
            String UserInput = null;

            do
            {
                Console.WriteLine("Please enter the board's {0} amount (must be 4 - 6) :", i_DimentionName);
                UserInput = userRequstToQuit(Console.ReadLine());
                if (int.TryParse(UserInput, out ReturnValue))
                {
                    if (!CardGame.IsDimensionValid(ReturnValue))
                    {
                        Console.WriteLine("Board dimention is not in proper size (4 - 6), please try again");
                    }
                    else
                    {
                        InputVerified = true;
                    }
                }
                else
                {
                    Console.WriteLine("Input entered is not a number, please try again");
                }
            } while (!InputVerified);

            return ReturnValue;
        }

        /// <summary>
        /// This function asks and reads from the user the desired cell to flip.
        /// </summary>
        /// <remarks>
        /// 1. The function checks that the input it reads from the user is in legal format.
        /// 2. The function then checks that the input is within the board's bounds.
        /// 3. It also checks that the input designates an unflipped card on the game board.
        /// </remarks>
        /// <seealso cref="RunGame()">
        /// The function getPlayerSelectedCell is being called from within the function RunGame() as part of the gameplay.
        /// </seealso>>
        /// <param name="o_UserSelectedPosition">
        /// This parameter gets the position of the desired game card to flip (by the user),
        ///     it's then being returned to the calling function
        /// </param>
        private void getPlayerSelectedCell(out BoardPosition o_UserSelectedPosition)
        {
            String UserInput = null;
            bool ChoosenCurrectCell = false;

            o_UserSelectedPosition = null;
            do
            {
                Console.WriteLine("Enter Cell to Open, in format  letter and number (example B2):");
                UserInput = userRequstToQuit(Console.ReadLine());
                if (UserInput.ToUpper().Equals("Q"))
                {
                   break;
                }
                else if (UserInput.Length != 2)
                {
                    Console.WriteLine("Error Input is not in format");
                    ChoosenCurrectCell = false;
                }
                else
                {
                    if (Char.IsLetter(UserInput[0]) && Char.IsDigit(UserInput[1]))
                    {
                        o_UserSelectedPosition = new BoardPosition(0, 0);
                        o_UserSelectedPosition.Column = (int)(char.ToUpper(UserInput[0]) - 'A');
                        o_UserSelectedPosition.Row = (int)(UserInput[1] - '0') - 1 ;
                        if (!CardGame.IsWithinLegalBounds(o_UserSelectedPosition))
                        {
                            Console.WriteLine("Error not in Board bounds");
                        
                        }
                        else if(CardGame.IsCellRevealed(o_UserSelectedPosition))
                        {
                            Console.WriteLine("Error The Card Is Open");
                        }
                        else
                        {
                            ChoosenCurrectCell = true;
                        }
                    }
                    else
                    {
                        Console.WriteLine("Error Input is not in format");
                    }

                }
            } while (!ChoosenCurrectCell);
        }

        /// <summary>
        /// This function draws the card game on the windows console screen.
        /// </summary>
        /// <param name="i_BoardToPrint">
        /// The function's only parameter is the cards table which contains the information on the game
        /// i.e. which cards are opened, and their position.
        /// </param>
        private void drawCardMatrix(CardsTable i_BoardToPrint)
        {
            BoardPosition PositionToPrint= new BoardPosition(0,0);
            Char BaseLetterToPrint = 'A';
            int Column = 0;
            int Row = 0; 
            
            Console.Write(" ");
            for (Column = 0; Column < i_BoardToPrint.BoardWidth; ++Column)
            {
                Console.Write("   {0}", BaseLetterToPrint.ToString());
                BaseLetterToPrint++;
            }

            Console.WriteLine();
            Console.Write("  ");
            for (Column = 0; Column <= (i_BoardToPrint.BoardWidth * 4); ++Column)
            {
                Console.Write("=");
            }

            Console.WriteLine();
            for (Row = 1; Row <= i_BoardToPrint.BoardHeight; ++Row)
            {
                Console.Write("{0} |", Row);
                for (Column = 1; Column <= i_BoardToPrint.BoardWidth; ++Column)
                {
                    PositionToPrint.Column = Column - 1;
                    PositionToPrint.Row = Row-1;
                    if (i_BoardToPrint.IsCellRevealed(PositionToPrint))
                    {
                        Console.Write(" {0} |", i_BoardToPrint.GetCellValue(PositionToPrint));
                    }
                    else
                    {
                        Console.Write("   |");
                    }
                }

                Console.WriteLine();
                Console.Write("  "); 
                for (int e = 0; e <= (i_BoardToPrint.BoardWidth * 4); ++e)
                {
                    Console.Write("=");
                }

                Console.WriteLine();
            }
        }

        private bool doesUserWantsAnotherMatch(out int o_BoardRows, out int o_BoardColumns)
        {
            bool ReturnValue = false;
            bool WasInputLegal = true;
            string UserInput = null;

            o_BoardColumns = 0;
            o_BoardRows = 0;
            do
            {
                Console.WriteLine("Do you want another match ? (please enter Y or N)");
                UserInput = userRequstToQuit(Console.ReadLine());
                if (UserInput.ToUpper() == "Y")
                {
                    ReturnValue = true;
                    WasInputLegal = true;
                    Ex02.ConsoleUtils.Screen.Clear();
                    getBoardDimentionsFromUser(out o_BoardRows, out o_BoardColumns);
                    CardGame.CreateBoard(o_BoardRows, o_BoardColumns);
                    Ex02.ConsoleUtils.Screen.Clear();
                }
                else if (UserInput.ToUpper() == "N")
                {
                    ReturnValue = false;
                    WasInputLegal = true;
                }
                else
                {
                    Console.WriteLine("Error : input must be Y or N, please try again");
                    WasInputLegal = false;
                }
            } while(!WasInputLegal);
            
            return ReturnValue;
        }

        private string userRequstToQuit(String i_UserInputStr)
        {
            string ReturnValue = i_UserInputStr;

            if (i_UserInputStr.ToUpper() == "Q")
            {
                Environment.Exit(0);
            }

            return ReturnValue;
        }
    }
}
